/*
 * May 29, 2005
 */
package codeanticode.clphysics;

import static java.lang.System.out;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.*;

import processing.core.PApplet;
import processing.opengl2.PGraphicsOpenGL2;
import processing.opengl2.PShape3D;

import com.jogamp.opencl.CLBuffer;
import com.jogamp.opencl.CLCommandQueue;
import com.jogamp.opencl.CLContext;
import com.jogamp.opencl.CLDevice;
import com.jogamp.opencl.CLPlatform;
import com.jogamp.opencl.gl.CLGLBuffer;
import com.jogamp.opencl.gl.CLGLContext;

public class ParticleSystem {
  public static final int MAX_PARTICLES = 10000;

  public static final int RUNGE_KUTTA = 0;
  public static final int MODIFIED_EULER = 1;

  protected static final float DEFAULT_GRAVITY = 0;
  protected static final float DEFAULT_DRAG = 0.001f;

  ArrayList particles;
  ArrayList springs;
  ArrayList attractions;
  ArrayList customForces = new ArrayList();

  Integrator integrator;

  Vector3D gravity;
  float drag;

  boolean hasDeadParticles = false;

  // Context for CL-GL sharing.
  CLGLContext context2;

  CLContext context;
  CLDevice device;
  CLCommandQueue queue;

  PApplet p;
  PGraphicsOpenGL2 ogl2;
  PShape3D particles2;
  CLGLBuffer<?> clVertices;
  /*
  CLBuffer<FloatBuffer> clForces;
  CLBuffer<FloatBuffer> clMasses;
  CLBuffer<IntBuffer> clStates;
  */

  public final void setIntegrator(int integrator) {
    switch (integrator) {
    case RUNGE_KUTTA:
      this.integrator = new RungeKuttaIntegrator(this);
      break;
    case MODIFIED_EULER:
      this.integrator = new ModifiedEulerIntegrator(this);
      break;
    }
  }

  public final void setGravity(float x, float y, float z) {
    gravity.set(x, y, z);
  }

  // default down gravity
  public final void setGravity(float g) {
    gravity.set(0, g, 0);
  }

  public final void setDrag(float d) {
    drag = d;
  }

  public final void tick() {
    tick(1);
  }

  public final void tick(float t) {
    integrator.step(t);
  }

  public final Particle makeParticle(float mass, float x, float y, float z) {
    Particle p = new Particle(mass);
    p.position().set(x, y, z);
    particles.add(p);
    return p;
  }

  public final Particle makeParticle() {
    return makeParticle(1.0f, 0f, 0f, 0f);
  }

  public final Spring makeSpring(Particle a, Particle b, float ks, float d,
      float r) {
    Spring s = new Spring(a, b, ks, d, r);
    springs.add(s);
    return s;
  }

  public final Attraction makeAttraction(Particle a, Particle b, float k,
      float minDistance) {
    Attraction m = new Attraction(a, b, k, minDistance);
    attractions.add(m);
    return m;
  }

  public final void clear() {
    particles.clear();
    springs.clear();
    attractions.clear();
  }

  public ParticleSystem(PApplet parent, float g, float somedrag) {
    this.p = parent;
    integrator = new RungeKuttaIntegrator(this);
    particles = new ArrayList();
    springs = new ArrayList();
    attractions = new ArrayList();
    gravity = new Vector3D(0, g, 0);
    drag = somedrag;
    initCL();
  }

  public ParticleSystem(PApplet parent, float gx, float gy, float gz,
      float somedrag) {
    this.p = parent;
    integrator = new RungeKuttaIntegrator(this);
    particles = new ArrayList();
    springs = new ArrayList();
    attractions = new ArrayList();
    gravity = new Vector3D(gx, gy, gz);
    drag = somedrag;
    initCL();
  }

  public ParticleSystem(PApplet parent) {
    this.p = parent;
    integrator = new RungeKuttaIntegrator(this);
    particles = new ArrayList();
    springs = new ArrayList();
    attractions = new ArrayList();
    gravity = new Vector3D(0, ParticleSystem.DEFAULT_GRAVITY, 0);
    drag = ParticleSystem.DEFAULT_DRAG;

    initCL();
  }

  public final void dispose() {
    finishCL();
  }

  protected final void initCL() {
    context = CLContext.create();
    out.println("created " + context);

    // select fastest device
    device = context.getMaxFlopsDevice();
    out.println("using " + device);

    // create command queue on device.
    queue = device.createCommandQueue();

    /*
     * // find gl compatible device CLDevice[] devices =
     * CLPlatform.getDefault().listCLDevices(); CLDevice device = null; for
     * (CLDevice d : devices) { if(d.isGLMemorySharingSupported()) { device = d;
     * break; } } if(null==device) { throw new
     * RuntimeException("couldn't find any CL/GL memory sharing devices .."); }
     * // create OpenCL context before creating any OpenGL objects // you want
     * to share with OpenCL (AMD driver requirement) context =
     * CLGLContext.create(ogl2.getContext(), device);
     */

    /*
    particles2 = new PShape3D(p, MAX_PARTICLES, PShape3D.newParameters());
    clVertices = context2.createFromGLBuffer(particles2.glVertexBufferID, CLGLBuffer.Mem.WRITE_ONLY);

    CLBuffer<FloatBuffer> clForces;
    CLBuffer<FloatBuffer> clMasses;
    CLBuffer<IntBuffer> clStates;
    */
  }

  protected final void finishCL() {
    context.release();
  }

  protected final void applyForces() {
    if (!gravity.isZero()) {
      for (int i = 0; i < particles.size(); ++i) {
        Particle p = (Particle) particles.get(i);
        p.force.add(gravity);
      }
    }

    for (int i = 0; i < particles.size(); ++i) {
      Particle p = (Particle) particles.get(i);
      p.force.add(p.velocity.x() * -drag, p.velocity.y() * -drag,
          p.velocity.z() * -drag);
    }

    for (int i = 0; i < springs.size(); i++) {
      Spring f = (Spring) springs.get(i);
      f.apply();
    }

    for (int i = 0; i < attractions.size(); i++) {
      Attraction f = (Attraction) attractions.get(i);
      f.apply();
    }

    for (int i = 0; i < customForces.size(); i++) {
      Force f = (Force) customForces.get(i);
      f.apply();
    }
  }

  protected final void clearForces() {
    Iterator i = particles.iterator();
    while (i.hasNext()) {
      Particle p = (Particle) i.next();
      p.force.clear();
    }
  }

  public final int numberOfParticles() {
    return particles.size();
  }

  public final int numberOfSprings() {
    return springs.size();
  }

  public final int numberOfAttractions() {
    return attractions.size();
  }

  public final Particle getParticle(int i) {
    return (Particle) particles.get(i);
  }

  public final Spring getSpring(int i) {
    return (Spring) springs.get(i);
  }

  public final Attraction getAttraction(int i) {
    return (Attraction) attractions.get(i);
  }

  public final void addCustomForce(Force f) {
    customForces.add(f);
  }

  public final int numberOfCustomForces() {
    return customForces.size();
  }

  public final Force getCustomForce(int i) {
    return (Force) customForces.get(i);
  }

  public final Force removeCustomForce(int i) {
    return (Force) customForces.remove(i);
  }

  public final void removeParticle(Particle p) {
    particles.remove(p);
  }

  public final Spring removeSpring(int i) {
    return (Spring) springs.remove(i);
  }

  public final Attraction removeAttraction(int i) {
    return (Attraction) attractions.remove(i);
  }

  public final void removeAttraction(Attraction s) {
    attractions.remove(s);
  }

  public final void removeSpring(Spring a) {
    springs.remove(a);
  }

  public final void removeCustomForce(Force f) {
    customForces.remove(f);
  }

}